/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			GuiControl.d
	Description:	basic graphic user interface classes
	Date:			17.05.2008 by Digited
*/
module moonglide.gui.GuiControl;

import
	tango.util.container.LinkedList,

	moonglide.util.MoonUtil,

	moonglide.core.Controllers,
	moonglide.core.Events,
	moonglide.core.Factory,
	moonglide.core.engobjects.EngObject,
	moonglide.core.engobjects.EngRawData,

	moonglide.gui.GuiCursor;

typedef void delegate( CGuiControl, bool ) renderingStrategy;

/* scale tech: each control gets size+position in percent and scalable flag. It compares percents with current
viewport (from gui manager) and calculates integer coords and size. If scalable, than it changes int size
when receive scale event. It also recalculates position percents when moved. Simple and effective stuff */

enum EGuiControlPart
{
	gcpButton = 1
}

//============================================================
/// standart part for rendering
//============================================================
struct SGuiControlPart
{
	mixin TDynamicProperties;

	EGuiControlPart type;

	Size2ui			size,
					position;
}


//=============================================================
/// basic control settings
//=============================================================
class CGuiControlSettings : CProductSettings
{
	protected:
		this() {}

	public:
		static CGuiControlSettings opCall( char[] id, Point2f pos, Point2f size,
			bool scalable, bool enabled, IEventListener listener, char[] parentID )
		in
		{
			moonAssert( listener !is null, "listener is null", __FILE__, __LINE__ );
		}
		body
		{
			auto settings = new CGuiControlSettings;

			with( settings )
			{
				strings[ "id" ] = id;
				strings[ "parent_id" ] = parentID;
				floats[ "pos_x" ] = pos.x;
				floats[ "pos_y" ] = pos.y;
				floats[ "size_w" ] = size.x;
				floats[ "size_h" ] = size.y;
				bools[ "scalable" ] = scalable;
				bools[ "enabled" ] = enabled;
				objects[ "listener" ] = cast(Object)listener;
			}

			return settings;
		}
}

//=============================================================
/// basic control factory
//=============================================================
class CGuiControlFactory : CProductFactory
{
	protected:
		// recursive construction with id as default parameter - used to register factories by id
		this( char[] id = "CGuiControlFactory" )
		{
			super( id );
		}

	public:
		CGuiControl opCall( CGuiControlSettings settings, CGuiControl product = null )
		{
			if( product is null )
			{
				// here descendents must construct their product. As CGuiControl is abstract, here we can't do it
				// product = new CGuiControl(...);
			}
			else
				with( product )
				{
					strings[ "parent_id" ] = settings.strings[ "parent_id" ];

					_scalable = settings.bools[ "scalable" ];
					_visible = settings.bools[ "visible" ];

					_size_pc = Point2f( settings.floats[ "size_w" ], settings.floats[ "size_h" ] );
					_position_pc = Point2f( settings.floats[ "pos_x" ], settings.floats[ "pos_y" ] );

					_cursor = cast(CGuiCursor)settings.objects[ "cursor" ];
					debug moonAssert( _cursor !is null, "cursor is null", __FILE__, __LINE__ );

					IEventListener listener = cast( IEventListener )settings.objects[ "listener" ];
					debug moonAssert( listener !is null, "listener is null", __FILE__, __LINE__ );
					registerListenerAll( listener );

					_CalcInts;
				}

			return product;
		}
}

//=============================================================
/// basic control class. Can be instantiated only with its factory with its settings
//=============================================================
class CGuiControl : public CEngRawData, IProduct
{
	mixin TEventGenerator;

	protected:
		//============================================================
		/// protected contructor - IProduct must be produceable only by its factory
		//============================================================
		this( char[] id )
		{
			super( id );
			_size_pc = _position_pc = _scale = Point2f( 0.f, 0.f ); // to avoid bugs with NAN
			_event = new CEvent( eeventtype.gui, eguievent.created );
			generateEvent( _event ); // to already registered (see factory) listener
		}

		bool		_ms_focused,
					_kb_focused,
					_scalable;

		Point2f		_size_pc,
					_position_pc,
					_scale;

		CGuiCursor	_cursor;
		CEvent		_event;

		// These funcs in descendents must contain control logic.
		// Added to avoid need of reloading onEventCallback.
		void _On_ms_enter()			{}
		void _On_ms_hover()			{}
		void _On_ms_button()		{}
		void _On_ms_exit()			{}
		void _On_kb_focused()		{}
		void _On_kb_key()			{}
		void _On_kb_unfocused() 	{}
		void _On_action()			{}
		void _On_visible_change()	{}

		void _CalcInts()
		{
			// calc int size and pos
			_size.set( _cursor.viewportSize.x * _size_pc.x, _cursor.viewportSize.y * _size_pc.y, 0.f );
			position.set( _cursor.viewportSize.x * _position_pc.x, _cursor.viewportSize.y * _position_pc.y, 0.f );
		}

	public:
		SGuiControlPart[]	parts;
		renderingStrategy	rendStrategy;

		//============================================================
		/// OnRender calls for rendering strategy to prepare control for fast rendering
		//============================================================
		override void onRender()
		{
			if( rendStrategy !is null )
				rendStrategy( this, true );
		}

//		//============================================================
//		/// opCall - use to construct an object. Disabled because of abstraction
//		//============================================================
//		static CGuiControl opCall( CGuiControlSettings settings )
//		{
//			return CGuiControlFactory( settings );
//		}

		override void Serialize()
		{
			// TODO (Digited#): subj
		}

		//================================================================
		/// check if point is above this control. Shape specific (rounded corners check, for example)
		//================================================================
		override bool isPointAbove( Size2ui p )
		{
			// here is simple rectangle check.
			// NOTE: It also can be used as first stage of shape scecific check.
			return (p.x >= wposition.x) && (p.x < wposition.x + _size.x) &&
				(p.y >= wposition.y) && (p.y < wposition.y + _size.y);
		}

		//================================================================
		/// input
		//================================================================
		override void onEventCallback( CEvent event, IEventGenerator sender )
		{
			switch( event.type )
			{
				case eeventtype.system:
					switch( event.subtype )
					{
						case esystemevent.msmove:
							// check cursor pos and our state
							if( !_visible )
								return;

							if( isPointAbove( _cursor.Position ))
							{
								foreach( child; children )
								{
									if( child.isPointAbove( _cursor.Position ))
									{
										// exit - send ms_enter to child under cursor
										_event.clearData();
										_event.type =  eeventtype.gui;
										_event.subtype = eguievent.ms_enter;
										child.onEventCallback( _event, sender );
										_On_ms_exit();
										return;
									}
								}

								if( _ms_focused )
								{
									// hover
									_event.clearData();
									_event.type =  eeventtype.gui;
									_event.subtype = eguievent.ms_hover;
									generateEvent( _event );
									_On_ms_hover();
								}
								else
								{
									// enter
									_event.clearData();
									_event.type =  eeventtype.gui;
									_event.subtype = eguievent.ms_enter;
									generateEvent( _event );
									_On_ms_enter();
								}
							}
							else
							{
								// exit. Send to parent, it now must delegate changing of mouse focused control
								debug moonAssert( parent !is null, "parent of gui control " ~ _id ~ " is null", __FILE__, __LINE__ );
								parent.onEventCallback( event, sender );

								_event.clearData();
								_event.type =  eeventtype.gui;
								_event.subtype = eguievent.ms_exit;
								generateEvent( _event );
								_On_ms_exit();
							}
							break;

						case esystemevent.kbkey:
							if( !_kb_focused )
							{
								_event.type =  eeventtype.gui;
								_event.subtype = eguievent.kb_focused;
								generateEvent( _event );
								_On_kb_focused();
							}

							_event.type =  eeventtype.gui;
							_event.subtype = eguievent.kb_key;
							generateEvent( _event );
							_On_kb_key();
							return;

						default: break;
					}
					break;

				case eeventtype.gui:
					switch( event.subtype )
					{
						case eguievent.ms_enter:
							foreach( child; children )
								if( child.isPointAbove( _cursor.Position ))
								{
									// exit - send ms_enter to child under cursor
									_event.clearData();
									_event.type =  eeventtype.gui;
									_event.subtype = eguievent.ms_enter;
									child.onEventCallback( _event, sender );
									_On_ms_exit();
									return;
								}

							_event.clearData();
							_event.type =  eeventtype.gui;
							_event.subtype = eguievent.ms_enter;
							generateEvent( _event );
							_On_ms_enter();
							return;

						case eguievent.scaling:
							if( _scalable )
							{
								_scale.set( event.floats[ "scale_x" ], event.floats[ "scale_y" ] );

								_size.set(
									_cursor.viewportSize.x * _size_pc.x * _scale.x,
									_cursor.viewportSize.y * _size_pc.y * _scale.y, 0.f );
								position.set(
									_cursor.viewportSize.x * _position_pc.x * _scale.x,
									_cursor.viewportSize.y * _position_pc.y * _scale.y, 0.f );
							}

							foreach( child; children )
								child.onEventCallback( event, sender );

							return;

						default: break;
					}
					break;

				case eeventtype.program:
					switch( event.subtype )
					{
						case eprogramevent.resize: // viewport resizing
							_size.set(
								_cursor.viewportSize.x * _size_pc.x * _scale.x,
								_cursor.viewportSize.y * _size_pc.y * _scale.y, 0.f );
							position.set(
								_cursor.viewportSize.x * _position_pc.x * _scale.x,
								_cursor.viewportSize.y * _position_pc.y * _scale.y, 0.f );

							foreach( child; children )
								child.onEventCallback( event, sender );

							break;
					}
					break;

				default: break;
			}
		}

		override void onEventReceive( CEvent event, IEventSender sender ) { onEventCallback( event, this ); }

		//============================================================
		/// destructor
		//============================================================
		~this()
		{
			// EngObject's destructor will delete all children automatically
			_event.clearData();
			_event.type =  eeventtype.gui;
			_event.subtype = eguievent.destroyed;
			generateEvent( _event );

			delete _event;
		}
}
